Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
promise-toolbox
Advanced tools
Essential utils for promises.
Features:
Table of contents:
Installation of the npm package:
> npm install --save promise-toolbox
You can directly use the build provided at unpkg.com:
<script src="https://unpkg.com/promise-toolbox@0.8/dist/umd.js"></script>
If your environment may not natively support promises, you should use a polyfill such as native-promise-only.
On Node, if you want to use a specific promise implementation, Bluebird for instance to have better performance, you can override the global Promise variable:
global.Promise = require("bluebird");
Note that it should only be done at the application level, never in a library!
You can either import all the tools directly:
import * as PT from "promise-toolbox";
console.log(PT.isPromise(value));
Or import individual tools from the main module:
import { isPromise } from "promise-toolbox";
console.log(isPromise(value));
Each tool is also exported with a p
prefix to work around reserved keywords
and to help differentiate with other tools (like lodash.map
):
import { pCatch, pMap } from "promise-toolbox";
If you are bundling your application (Browserify, Rollup, Webpack, etc.), you can cherry-pick the tools directly:
import isPromise from "promise-toolbox/isPromise";
import pCatch from "promise-toolbox/catch";
This library provides an implementation of CancelToken
from the
cancelable promises specification.
A cancel token is an object which can be passed to asynchronous functions to represent cancelation state.
import { CancelToken } from "promise-toolbox";
A cancel token is created by the initiator of the async work and its cancelation state may be requested at any time.
// Create a token which requests cancelation when a button is clicked.
const token = new CancelToken(cancel => {
$("#some-button").on("click", () => cancel("button clicked"));
});
const { cancel, token } = CancelToken.source();
A list of existing tokens can be passed to source()
to make the created token
follow their cancelation:
// `source.token` will be canceled (synchronously) as soon as `token1` or
// `token2` or token3` is, with the same reason.
const { cancel, token } = CancelToken.source([token1, token2, token3]);
The receiver of the token (the function doing the async work) can:
// 1.
if (token.reason) {
console.log("cancelation has been requested", token.reason.message);
}
// 2.
try {
token.throwIfRequested();
} catch (reason) {
console.log("cancelation has been requested", reason.message);
}
// 3.
token.promise.then(reason => {
console.log("cancelation has been requested", reason.message);
});
// 4.
subtask(token);
Asynchronous handlers are executed on token cancelation and the promise returned by the
cancel
function will wait for all handlers to settle.
function httpRequest(cancelToken, opts) {
const req = http.request(opts);
req.end();
cancelToken.addHandler(() => {
req.abort();
// waits for the socket to really close for the cancelation to be
// complete
return fromEvent(req, "close");
});
return fromEvent(req, "response");
}
const { cancel, token } = CancelToken.source();
httpRequest(token, {
hostname: "example.org",
}).then(response => {
// do something with the response of the request
});
// wraps with Promise.resolve() because cancel only returns a promise
// if a handler has returned a promise
Promise.resolve(cancel()).then(() => {
// the request has been properly canceled
});
if (CancelToken.isCancelToken(value)) {
console.log("value is a cancel token");
}
Make your async functions cancelable.
If the first argument passed to the cancelable function is not a
cancel token, a new one is created and injected and the returned
promise will have a cancel()
method.
import { cancelable, CancelToken } from "promise-toolbox";
const asyncFunction = cancelable(async ($cancelToken, a, b) => {
$cancelToken.promise.then(() => {
// do stuff regarding the cancelation request.
});
// do other stuff.
});
// Either a cancel token is passed:
const source = CancelToken.source();
const promise1 = asyncFunction(source.token, "foo", "bar");
source.cancel("reason");
// Or the returned promise will have a cancel() method:
const promise2 = asyncFunction("foo", "bar");
promise2.cancel("reason");
If the function is a method of a class or an object, you can use
cancelable
as a decorator:
class MyClass {
@cancelable
async asyncMethod($cancelToken, a, b) {
// ...
}
}
See Bluebird documentation for a good explanation.
import { disposer, using } from 'promise-toolbox'
const getConnection = () =>
// disposer() is used to associate a disposer to a resource
//
// The returned resource can only be used with using()
db.connect()::disposer(connection =>
connection.close()
)
using(getConnection(), getConnection(), (connection1, connection2) => {
// So something with connection1 and connection2
})).then(() => {
// Both connections are now closed
})
Create an async function from a generator function
Similar to
Bluebird.coroutine
.
import { asyncFn } from 'promise-toolbox'
const getUserName = asyncFn(function * (db, userId)) {
const user = yield db.getRecord(userId)
return user.name
})
Like
asyncFn(generator)
but the created async function supports cancelation.Similar to CAF.
import { asyncFn, CancelToken } from 'promise-toolbox'
const getUserName = asyncFn.cancelable(function * (cancelToken, db, userId)) {
// this yield will throw if the cancelToken is activated
const user = yield db.getRecord(userId)
return user.name
})
const source = CancelToken.source()
getUserName(source.token, db, userId).then(
name => {
console.log('user name is', name)
},
error => {
console.error(error)
}
)
// only wait 5 seconds to fetch the user from the database
setTimeout(source.cancel, 5e3)
Discouraged but sometimes necessary way to create a promise.
import { defer } from "promise-toolbox";
const { promise, resolve } = defer();
promise.then(value => {
console.log(value);
});
resolve(3);
Easiest and most efficient way to promisify a function call.
import { fromCallback } from "promise-toolbox";
// callback is appended to the list of arguments passed to the function
fromCallback(fs.readFile, "foo.txt").then(content => {
console.log(content);
});
// if the callback does not go at the end, you can wrap the call
fromCallback(cb => foo("bar", cb, "baz")).then(() => {
// ...
});
// you can use `.call` to specify the context of execution
fromCallback.call(thisArg, fn, ...args).then(() => {
// ...
});
// finally, if you want to call a method, you can pass its name instead of a
// function
fromCallback.call(object, "method", ...args).then(() => {
// ...
});
Wait for one event. The first parameter of the emitted event is used to resolve/reject the promise.
const promise = fromEvent(emitter, "foo", {
// whether the promise resolves to an array of all the event args
// instead of simply the first arg
array: false,
// whether the error event can reject the promise
ignoreErrors: false,
// name of the error event
error: "error",
});
promise.then(
value => {
console.log("foo event was emitted with value", value);
},
reason => {
console.error("an error has been emitted", reason);
}
);
Wait for one of multiple events. The array of all the parameters of the emitted event is used to resolve/reject the promise.
The array also has an
event
property indicating which event has been emitted.
fromEvents(emitter, ["foo", "bar"], ["error1", "error2"]).then(
event => {
console.log(
"event %s have been emitted with values",
event.name,
event.args
);
},
reasons => {
console.error(
"error event %s has been emitted with errors",
event.names,
event.args
);
}
);
import { isPromise } from "promise-toolbox";
if (isPromise(foo())) {
console.log("foo() returns a promise");
}
Create a new function from the composition of async functions.
import { pipe } from "promise-toolbox";
const getUserPreferences = pipe(
getUser,
getPreferences
);
Creates async functions taking node-style callbacks, create new ones returning promises.
import fs from "fs";
import { promisify, promisifyAll } from "promise-toolbox";
// Promisify a single function.
//
// If possible, the function name is kept and the new length is set.
const readFile = promisify(fs.readFile);
// Or all functions (own or inherited) exposed on a object.
const fsPromise = promisifyAll(fs);
readFile(__filename).then(content => console.log(content));
fsPromise.readFile(__filename).then(content => console.log(content));
Retries an async function when it fails.
import { retry } from "promise-toolbox";
(async () => {
await retry(
async bail => {
const response = await fetch("https://pokeapi.co/api/v2/pokemon/3/");
if (response.status === 500) {
// no need to retry in this case
throw bail(new Error(response.statusText));
}
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.json();
},
{
// delay before a retry, default to 1000 ms
delay: 2000,
// this function is called before a retry is schedule (before the delay)
//
// it receives the error that triggered the retry
async onRetry(error) {
// …
},
// number of tries including the first one, default to 10
//
// cannot be used with `retries`
tries: 3,
// number of retries (excluding the initial run), default to undefined
//
// cannot be used with `tries`
retries: 4,
// predicate when to retry, default on always but programmer errors
// (ReferenceError, SyntaxError and TypeError)
//
// similar to `promise-toolbox/catch`, it can be a constructor, an object,
// a function, or an array of the previous
when: { message: "my error message" },
}
);
})().catch(console.error.bind(console));
Starts a chain of promises.
import PromiseToolbox from "promise-toolbox";
const getUserById = id =>
PromiseToolbox.try(() => {
if (typeof id !== "number") {
throw new Error("id must be a number");
}
return db.getUserById(id);
});
Note: similar to
Promise.resolve().then(fn)
but callsfn()
synchronously.
Wrap a call to a function to always return a promise.
function getUserById(id) {
if (typeof id !== "number") {
throw new TypeError("id must be a number");
}
return db.getUser(id);
}
wrapCall(getUserById, "foo").catch(error => {
// id must be a number
});
This function can be used as if they were methods, i.e. by passing the promise (or promises) as the context.
This is extremely easy using ES2016's bind syntax.
const promises = [Promise.resolve("foo"), Promise.resolve("bar")];
promises::all().then(values => {
console.log(values);
});
// → [ 'foo', 'bar' ]
If you are still an older version of ECMAScript, fear not: simply pass
the promise (or promises) as the first argument of the .call()
method:
const promises = [Promise.resolve("foo"), Promise.resolve("bar")];
all.call(promises).then(function(values) {
console.log(values);
});
// → [ 'foo', 'bar' ]
Waits for all promises of a collection to be resolved.
Contrary to the standard
Promise.all()
, this function works also with objects.
import { all } from 'promise-toolbox'
[
Promise.resolve('foo'),
Promise.resolve('bar')
]::all().then(value => {
console.log(value)
// → ['foo', 'bar']
})
{
foo: Promise.resolve('foo'),
bar: Promise.resolve('bar')
}::all().then(value => {
console.log(value)
// → {
// foo: 'foo',
// bar: 'bar'
// }
})
Register a node-style callback on this promise.
import { asCallback } from "promise-toolbox";
// This function can be used either with node-style callbacks or with
// promises.
function getDataFor(input, callback) {
return dataFromDataBase(input)::asCallback(callback);
}
Similar to
Promise#catch()
but:
- support predicates
- do not catch
ReferenceError
,SyntaxError
orTypeError
unless they match a predicate because they are usually programmer errors and should be handled separately.
somePromise
.then(() => {
return a.b.c.d();
})
::pCatch(TypeError, ReferenceError, reason => {
// Will end up here on programmer error
})
::pCatch(NetworkError, TimeoutError, reason => {
// Will end up here on expected everyday network errors
})
::pCatch(reason => {
// Catch any unexpected errors
});
Delays the resolution of a promise by
ms
milliseconds.Note: the rejection is not delayed.
console.log(await Promise.resolve("500ms passed")::delay(500));
// → 500 ms passed
Also works with a value:
console.log(await delay(500, "500ms passed"));
// → 500 ms passed
Like setTimeout
in Node, it is possible to
unref
the timer:
await delay(500).unref();
Iterates in order over a collection, or promise of collection, which contains a mix of promises and values, waiting for each call of cb to be resolved before the next one.
The returned promise will resolve to undefined
when the iteration is
complete.
["foo", Promise.resolve("bar")]::forEach(value => {
console.log(value);
// Wait for the promise to be resolve before the next item.
return new Promise(resolve => setTimeout(resolve, 10));
});
// →
// foo
// bar
Ignore (operational) errors for this promise.
import { ignoreErrors } from "promise-toolbox";
// will not emit an unhandled rejection error if the file does not
// exist
readFileAsync("foo.txt")
.then(content => {
console.log(content);
})
::ignoreErrors();
// will emit an unhandled rejection error due to the typo
readFileAsync("foo.txt")
.then(content => {
console.lgo(content); // typo
})
::ignoreErrors();
Execute a handler regardless of the promise fate. Similar to the
finally
block in synchronous codes.The resolution value or rejection reason of the initial promise is forwarded unless the callback rejects.
import { pFinally } from "promise-toolbox";
function ajaxGetAsync(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
})::pFinally(() => {
$("#ajax-loader-animation").hide();
});
}
Returns a promise which resolves to an objects which reflects the resolution of this promise.
import { reflect } from "promise-toolbox";
const inspection = await promise::reflect();
if (inspection.isFulfilled()) {
console.log(inspection.value());
} else {
console.error(inspection.reason());
}
Waits for
count
promises in a collection to be resolved.
import { some } from "promise-toolbox";
const [first, seconds] = await [
ping("ns1.example.org"),
ping("ns2.example.org"),
ping("ns3.example.org"),
ping("ns4.example.org"),
]::some(2);
Suppress unhandled rejections, needed when error handlers are attached asynchronously after the promise has rejected.
Similar to
Bluebird#suppressUnhandledRejections()
.
const promise = getUser()::suppressUnhandledRejections();
$(document).on("ready", () => {
promise.catch(error => {
console.error("error while getting user", error);
});
});
Like
.then()
but the original resolution/rejection is forwarded.Like
::finally()
, if the callback rejects, it takes over the original resolution/rejection.
import { tap } from "promise-toolbox";
// Contrary to .then(), using ::tap() does not change the resolution
// value.
const promise1 = Promise.resolve(42)::tap(value => {
console.log(value);
});
// Like .then, the second param is used in case of rejection.
const promise2 = Promise.reject(42)::tap(null, reason => {
console.error(reason);
});
Alias to
promise:tap(null, onRejected)
.
Call a callback if the promise is still pending after
ms
milliseconds. Its resolution/rejection is forwarded.If the callback is omitted, the returned promise is rejected with a
TimeoutError
.
import { timeout, TimeoutError } from "promise-toolbox";
await doLongOperation()::timeout(100, () => {
return doFallbackOperation();
});
await doLongOperation()::timeout(100);
await doLongOperation()::timeout(
100,
new Error("the long operation has failed")
);
Note:
0
is a special value which disable the timeout, useful if the delay is configurable in your app.
# Install dependencies
> npm install
# Run the tests
> npm test
# Continuously compile
> npm run dev
# Continuously run the tests
> npm run dev-test
# Build for production
> npm run build
Contributions are very welcomed, either on the documentation or on the code.
You may:
ISC © Julien Fontanet
FAQs
Essential utils for promises
The npm package promise-toolbox receives a total of 31,380 weekly downloads. As such, promise-toolbox popularity was classified as popular.
We found that promise-toolbox demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.